package com.sahyadri.web.util.filters;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import com.sahyadri.web.util.constant.SessionAttributeKeys;



/**
 * Filter implementation which simply checks for the existence of a flag in the HttpSession to determine if the
 * Session Init process has completed yet for the users session. If it has, request is passed on untouched, if it
 * hasn't, the user is redirected back to sessionInit url.
 */
public class SessionInitFilter implements Filter {

	private static final String DEFAULT_TARGET_URL_KEY = SessionAttributeKeys.TARGET_URL;
	private static final String DEFAULT_SESSION_INIT_KEY = SessionAttributeKeys.INIT_SESSION_FLAG;
	private static final String DEFAULT_SESSION_INIT_URL = "/sahyadri-web/rest/authentication/initSession";
	private final Logger log = Logger.getLogger(this.getClass());

	private String targetUrlKey = DEFAULT_TARGET_URL_KEY;
	private String sessionInitKey = DEFAULT_SESSION_INIT_KEY;
	private String sessionInitUrl = DEFAULT_SESSION_INIT_URL;

	private List<String> DEFAULT_IGNORE_PATTERNS = Arrays.asList(
			"**/public/**",
			"**/bundles/**",
			"**/images/**",
			"**/authentication/**",
			"**/*.js",
			"**/*.css",
			"**/*.png",
			"**/*.gif",
			"**/*.jpeg",
			"**/*.jpg"
			);

	private List<String> ignorePatterns = DEFAULT_IGNORE_PATTERNS;
	private PathMatcher pathMatcher = new AntPathMatcher();

	public String getSessionInitKey() {
		return sessionInitKey;
	}

	public void setSessionInitKey(String sessionInitKey) {
		this.sessionInitKey = sessionInitKey;
	}

	public String getSessionInitUrl() {
		return sessionInitUrl;
	}

	public void setSessionInitUrl(String sessionInitUrl) {
		this.sessionInitUrl = sessionInitUrl;
	}

	public List<String> getIgnorePatterns() {
		return ignorePatterns;
	}

	public void setIgnorePatterns(List<String> ignorePaths) {
		if (ignorePaths != null) {
			this.ignorePatterns = ignorePaths;
		}
	}

	/**
	 * Filter method used to determine if the session for the given request has been initialized yet.
	 * Ignores requested defined by {@link #setIgnorePatterns(List)}.
	 * If the path should be ignored or the session has already been initiated, the request is passed through
	 * untouched.
	 * If the session has not yet been initialized, the initial request url is saved in session and the request
	 * is redirected to the initSession controller.
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		HttpServletRequest hReq = (HttpServletRequest) request;
		HttpServletResponse hResp = (HttpServletResponse) response;

		String url = hReq.getRequestURL().toString();

		log.debug("Hit SessionInitFilter with for URL: '" + url + "'");

		if (shouldIgnore(url)) {
			log.debug("Url matched ignore filter pattern.  Bypassing SessionInit Check for url:'" + url + "'");
			chain.doFilter(request, hResp);
			return;
		}

		HttpSession session = hReq.getSession();
		boolean isSessionInitComplete = (session.getAttribute(sessionInitKey) != null);

		if (!isSessionInitComplete) {
			log.warn("Request recieved for a session that has not been initialized yet. Redirecting to session init URL ("
					+ this.sessionInitUrl + ").");
			// Include QueryString in target URL Redirect
			String queryString = hReq.getQueryString();
			if (queryString != null) {
				url += "?" + queryString;
			}
			session.setAttribute(targetUrlKey, url);
			hResp.sendRedirect(sessionInitUrl);
			return;
		}

		chain.doFilter(request, hResp);
	}

	/**
	 * Utility Method used to determine if a given URL should be ignored by this filter or not.
	 * Utilizes {@link AntPathMatcher} logic to determin if url should be ignored.
	 * 
	 * @param url
	 *            The URL to be checked against.
	 * @return Boolean indicating if the url matched the configured list of ignore patterns.
	 */
	protected boolean shouldIgnore(String url) {

		for (String pattern : ignorePatterns) {
			boolean isMatch = pathMatcher.match(pattern, url);
			if (log.isTraceEnabled()) {
				log.trace("Checked path '" + url + "' against pattern '" + pattern + "' Matched: " + isMatch);
			}

			if (isMatch) {
				// Short circuit on first match
				return true;
			}
		}
		// No matches found
		return false;
	}

	// Filter lifecycle call back not used...just declared to fulfill Filter contract
	public void destroy() {
	}

	public void init(FilterConfig fConfig) throws ServletException {
	}

}
